home *** CD-ROM | disk | FTP | other *** search
-
-
- package require Tcl 8.4
- package provide http 2.5.2
-
- namespace eval http {
- variable http
- array set http {
- -accept */*
- -proxyhost {}
- -proxyport {}
- -proxyfilter http::ProxyRequired
- -urlencoding utf-8
- }
- set http(-useragent) "Tcl http client package [package provide http]"
-
- proc init {} {
- for {set i 0} {$i <= 256} {incr i} {
- set c [format %c $i]
- if {![string match {[-._~a-zA-Z0-9]} $c]} {
- set map($c) %[format %.2x $i]
- }
- }
- array set map { " " + \n %0d%0a }
- variable formMap [array get map]
- }
- init
-
- variable urlTypes
- array set urlTypes {
- http {80 ::socket}
- }
-
- variable encodings [string tolower [encoding names]]
- variable defaultCharset "iso8859-1"
-
- namespace export geturl config reset wait formatQuery register unregister
- }
-
-
- proc http::register {proto port command} {
- variable urlTypes
- set urlTypes($proto) [list $port $command]
- }
-
-
- proc http::unregister {proto} {
- variable urlTypes
- if {![info exists urlTypes($proto)]} {
- return -code error "unsupported url type \"$proto\""
- }
- set old $urlTypes($proto)
- unset urlTypes($proto)
- return $old
- }
-
-
- proc http::config {args} {
- variable http
- set options [lsort [array names http -*]]
- set usage [join $options ", "]
- if {[llength $args] == 0} {
- set result {}
- foreach name $options {
- lappend result $name $http($name)
- }
- return $result
- }
- set options [string map {- ""} $options]
- set pat ^-([join $options |])$
- if {[llength $args] == 1} {
- set flag [lindex $args 0]
- if {[regexp -- $pat $flag]} {
- return $http($flag)
- } else {
- return -code error "Unknown option $flag, must be: $usage"
- }
- } else {
- foreach {flag value} $args {
- if {[regexp -- $pat $flag]} {
- set http($flag) $value
- } else {
- return -code error "Unknown option $flag, must be: $usage"
- }
- }
- }
- }
-
-
- proc http::Finish { token {errormsg ""} {skipCB 0}} {
- variable $token
- upvar 0 $token state
- global errorInfo errorCode
- if {[string length $errormsg] != 0} {
- set state(error) [list $errormsg $errorInfo $errorCode]
- set state(status) error
- }
- catch {close $state(sock)}
- catch {after cancel $state(after)}
- if {[info exists state(-command)] && !$skipCB} {
- if {[catch {eval $state(-command) {$token}} err]} {
- if {[string length $errormsg] == 0} {
- set state(error) [list $err $errorInfo $errorCode]
- set state(status) error
- }
- }
- if {[info exists state(-command)]} {
- unset state(-command)
- }
- }
- }
-
-
- proc http::reset { token {why reset} } {
- variable $token
- upvar 0 $token state
- set state(status) $why
- catch {fileevent $state(sock) readable {}}
- catch {fileevent $state(sock) writable {}}
- Finish $token
- if {[info exists state(error)]} {
- set errorlist $state(error)
- unset state
- eval ::error $errorlist
- }
- }
-
-
- proc http::geturl { url args } {
- variable http
- variable urlTypes
- variable defaultCharset
-
-
- if {![info exists http(uid)]} {
- set http(uid) 0
- }
- set token [namespace current]::[incr http(uid)]
- variable $token
- upvar 0 $token state
- reset $token
-
-
- array set state {
- -binary false
- -blocksize 8192
- -queryblocksize 8192
- -validate 0
- -headers {}
- -timeout 0
- -type application/x-www-form-urlencoded
- -queryprogress {}
- state header
- meta {}
- coding {}
- currentsize 0
- totalsize 0
- querylength 0
- queryoffset 0
- type text/html
- body {}
- status ""
- http ""
- }
- array set type {
- -binary boolean
- -blocksize integer
- -queryblocksize integer
- -validate boolean
- -timeout integer
- }
- set state(charset) $defaultCharset
- set options {-binary -blocksize -channel -command -handler -headers -progress -query -queryblocksize -querychannel -queryprogress -validate -timeout -type}
- set usage [join $options ", "]
- set options [string map {- ""} $options]
- set pat ^-([join $options |])$
- foreach {flag value} $args {
- if {[regexp $pat $flag]} {
- if {[info exists type($flag)] && ![string is $type($flag) -strict $value]} {
- unset $token
- return -code error "Bad value for $flag ($value), must be $type($flag)"
- }
- set state($flag) $value
- } else {
- unset $token
- return -code error "Unknown option $flag, can be: $usage"
- }
- }
-
-
- set isQueryChannel [info exists state(-querychannel)]
- set isQuery [info exists state(-query)]
- if {$isQuery && $isQueryChannel} {
- unset $token
- return -code error "Can't combine -query and -querychannel options!"
- }
-
-
-
- set URLmatcher {(?x) # this is _expanded_ syntax
- ^
- (?: (\w+) : ) ? # <protocol scheme>
- (?: //
- (?:
- (
- [^@/\#?]+ # <userinfo part of authority>
- ) @
- )?
- ( [^/:\#?]+ ) # <host part of authority>
- (?: : (\d+) )? # <port part of authority>
- )?
- ( / [^\#?]* (?: \? [^\#?]* )?)? # <path> (including query)
- (?: \# (.*) )? # <fragment>
- $
- }
-
- if {![regexp -- $URLmatcher $url -> proto user host port srvurl]} {
- unset $token
- return -code error "Unsupported URL: $url"
- }
- if {$host eq ""} {
- unset $token
- return -code error "Missing host part: $url"
- }
- if {$port ne "" && $port>65535} {
- unset $token
- return -code error "Invalid port number: $port"
- }
- if {$user ne ""} {
- set validityRE {(?xi)
- ^
- (?: [-\w.~!$&'()*+,;=:] | %[0-9a-f][0-9a-f] )+
- $
- }
- if {![regexp -- $validityRE $user]} {
- unset $token
- if {[regexp {(?i)%(?![0-9a-f][0-9a-f]).?.?} $user bad]} {
- return -code error "Illegal encoding character usage \"$bad\" in URL user"
- }
- return -code error "Illegal characters in URL user"
- }
- }
- if {$srvurl ne ""} {
- set validityRE {(?xi)
- ^
- (?: [-\w.~!$&'()*+,;=:@/] | %[0-9a-f][0-9a-f] )*
- (?: \? (?: [-\w.~!$&'()*+,;=:@/?] | %[0-9a-f][0-9a-f] )* )?
- $
- }
- if {![regexp -- $validityRE $srvurl]} {
- unset $token
- if {[regexp {(?i)%(?![0-9a-f][0-9a-f])..} $srvurl bad]} {
- return -code error "Illegal encoding character usage \"$bad\" in URL path"
- }
- return -code error "Illegal characters in URL path"
- }
- } else {
- set srvurl /
- }
- if {[string length $proto] == 0} {
- set proto http
- set url ${proto}:$url
- }
- if {![info exists urlTypes($proto)]} {
- unset $token
- return -code error "Unsupported URL type \"$proto\""
- }
- set defport [lindex $urlTypes($proto) 0]
- set defcmd [lindex $urlTypes($proto) 1]
-
- if {[string length $port] == 0} {
- set port $defport
- }
- if {![catch {$http(-proxyfilter) $host} proxy]} {
- set phost [lindex $proxy 0]
- set pport [lindex $proxy 1]
- }
-
- set url ${proto}://
- if {$user ne ""} {
- append url $user
- append url @
- }
- append url $host
- if {$port != $defport} {
- append url : $port
- }
- append url $srvurl
- set state(url) $url
-
-
- if {$state(-timeout) > 0} {
- set state(after) [after $state(-timeout) [list http::reset $token timeout]]
- set async -async
- } else {
- set async ""
- }
-
-
- if {[info exists phost] && [string length $phost]} {
- set srvurl $url
- set conStat [catch {eval $defcmd $async {$phost $pport}} s]
- } else {
- set conStat [catch {eval $defcmd $async {$host $port}} s]
- }
-
- if {$conStat} {
- Finish $token "" 1
- cleanup $token
- return -code error $s
- }
- set state(sock) $s
-
-
- if {$state(-timeout) > 0} {
- fileevent $s writable [list http::Connect $token]
- http::wait $token
-
- if {$state(status) eq "error"} {
- set err [lindex $state(error) 0]
- cleanup $token
- return -code error $err
- } elseif {$state(status) ne "connect"} {
- return $token
- }
- set state(status) ""
- }
-
-
- fconfigure $s -translation {auto crlf} -buffersize $state(-blocksize)
-
-
- catch {fconfigure $s -blocking off}
- set how GET
- if {$isQuery} {
- set state(querylength) [string length $state(-query)]
- if {$state(querylength) > 0} {
- set how POST
- set contDone 0
- } else {
- unset state(-query)
- set isQuery 0
- }
- } elseif {$state(-validate)} {
- set how HEAD
- } elseif {$isQueryChannel} {
- set how POST
- fconfigure $state(-querychannel) -blocking 1 -translation binary
- set contDone 0
- }
-
- if {[catch {
- puts $s "$how $srvurl HTTP/1.0"
- puts $s "Accept: $http(-accept)"
- if {$port == $defport} {
- puts $s "Host: $host"
- } else {
- puts $s "Host: $host:$port"
- }
- puts $s "User-Agent: $http(-useragent)"
- foreach {key value} $state(-headers) {
- set value [string map [list \n "" \r ""] $value]
- set key [string trim $key]
- if {$key eq "Content-Length"} {
- set contDone 1
- set state(querylength) $value
- }
- if {[string length $key]} {
- puts $s "$key: $value"
- }
- }
- if {$isQueryChannel && $state(querylength) == 0} {
-
- set start [tell $state(-querychannel)]
- seek $state(-querychannel) 0 end
- set state(querylength) [expr {[tell $state(-querychannel)] - $start}]
- seek $state(-querychannel) $start
- }
-
-
- if {$isQuery || $isQueryChannel} {
- puts $s "Content-Type: $state(-type)"
- if {!$contDone} {
- puts $s "Content-Length: $state(querylength)"
- }
- puts $s ""
- fconfigure $s -translation {auto binary}
- fileevent $s writable [list http::Write $token]
- } else {
- puts $s ""
- flush $s
- fileevent $s readable [list http::Event $token]
- }
-
- if {! [info exists state(-command)]} {
-
- wait $token
- if {$state(status) eq "error"} {
- return -code error [lindex $state(error) 0]
- }
- }
- } err]} {
-
-
- if {$state(status) eq "error"} {
- Finish $token $err 1
- }
- cleanup $token
- return -code error $err
- }
-
- return $token
- }
-
-
- proc http::data {token} {
- variable $token
- upvar 0 $token state
- return $state(body)
- }
- proc http::status {token} {
- variable $token
- upvar 0 $token state
- return $state(status)
- }
- proc http::code {token} {
- variable $token
- upvar 0 $token state
- return $state(http)
- }
- proc http::ncode {token} {
- variable $token
- upvar 0 $token state
- if {[regexp {[0-9]{3}} $state(http) numeric_code]} {
- return $numeric_code
- } else {
- return $state(http)
- }
- }
- proc http::size {token} {
- variable $token
- upvar 0 $token state
- return $state(currentsize)
- }
-
- proc http::error {token} {
- variable $token
- upvar 0 $token state
- if {[info exists state(error)]} {
- return $state(error)
- }
- return ""
- }
-
-
- proc http::cleanup {token} {
- variable $token
- upvar 0 $token state
- if {[info exists state]} {
- unset state
- }
- }
-
-
- proc http::Connect {token} {
- variable $token
- upvar 0 $token state
- global errorInfo errorCode
- if {[eof $state(sock)] ||
- [string length [fconfigure $state(sock) -error]]} {
- Finish $token "connect failed [fconfigure $state(sock) -error]" 1
- } else {
- set state(status) connect
- fileevent $state(sock) writable {}
- }
- return
- }
-
-
- proc http::Write {token} {
- variable $token
- upvar 0 $token state
- set s $state(sock)
-
- set done 0
- if {[catch {
-
- if {[info exists state(-query)]} {
-
- puts -nonewline $s [string range $state(-query) $state(queryoffset) [expr {$state(queryoffset) + $state(-queryblocksize) - 1}]]
- incr state(queryoffset) $state(-queryblocksize)
- if {$state(queryoffset) >= $state(querylength)} {
- set state(queryoffset) $state(querylength)
- set done 1
- }
- } else {
-
- set outStr [read $state(-querychannel) $state(-queryblocksize)]
- puts -nonewline $s $outStr
- incr state(queryoffset) [string length $outStr]
- if {[eof $state(-querychannel)]} {
- set done 1
- }
- }
- } err]} {
-
- set state(posterror) $err
- set done 1
- }
- if {$done} {
- catch {flush $s}
- fileevent $s writable {}
- fileevent $s readable [list http::Event $token]
- }
-
-
- if {[string length $state(-queryprogress)]} {
- eval $state(-queryprogress) [list $token $state(querylength) $state(queryoffset)]
- }
- }
-
-
- proc http::Event {token} {
- variable $token
- upvar 0 $token state
- set s $state(sock)
-
- if {[eof $s]} {
- Eof $token
- return
- }
- if {$state(state) eq "header"} {
- if {[catch {gets $s line} n]} {
- Finish $token $n
- } elseif {$n == 0} {
- variable encodings
- set state(state) body
- if {$state(-binary) || ![string match -nocase text* $state(type)]
- || [string match *gzip* $state(coding)]
- || [string match *compress* $state(coding)]} {
- fconfigure $s -translation binary
- if {[info exists state(-channel)]} {
- fconfigure $state(-channel) -translation binary
- }
- } else {
- set idx [lsearch -exact $encodings [string tolower $state(charset)]]
- if {$idx >= 0} {
- fconfigure $s -encoding [lindex $encodings $idx]
- }
- }
- if {[info exists state(-channel)] && ![info exists state(-handler)]} {
- fileevent $s readable {}
- CopyStart $s $token
- }
- } elseif {$n > 0} {
- if {[regexp -nocase {^content-type:(.+)$} $line x type]} {
- set state(type) [string trim $type]
- regexp -nocase {charset\s*=\s*(\S+)} $type x state(charset)
- }
- if {[regexp -nocase {^content-length:(.+)$} $line x length]} {
- set state(totalsize) [string trim $length]
- }
- if {[regexp -nocase {^content-encoding:(.+)$} $line x coding]} {
- set state(coding) [string trim $coding]
- }
- if {[regexp -nocase {^([^:]+):(.+)$} $line x key value]} {
- lappend state(meta) $key [string trim $value]
- } elseif {[string match HTTP* $line]} {
- set state(http) $line
- }
- }
- } else {
- if {[catch {
- if {[info exists state(-handler)]} {
- set n [eval $state(-handler) {$s $token}]
- } else {
- set block [read $s $state(-blocksize)]
- set n [string length $block]
- if {$n >= 0} {
- append state(body) $block
- }
- }
- if {$n >= 0} {
- incr state(currentsize) $n
- }
- } err]} {
- Finish $token $err
- } else {
- if {[info exists state(-progress)]} {
- eval $state(-progress) {$token $state(totalsize) $state(currentsize)}
- }
- }
- }
- }
-
-
- proc http::CopyStart {s token} {
- variable $token
- upvar 0 $token state
- if {[catch {
- fcopy $s $state(-channel) -size $state(-blocksize) -command [list http::CopyDone $token]
- } err]} {
- Finish $token $err
- }
- }
-
-
- proc http::CopyDone {token count {error {}}} {
- variable $token
- upvar 0 $token state
- set s $state(sock)
- incr state(currentsize) $count
- if {[info exists state(-progress)]} {
- eval $state(-progress) {$token $state(totalsize) $state(currentsize)}
- }
- if {[string length $error]} {
- Finish $token $error
- } elseif {[catch {eof $s} iseof] || $iseof} {
- Eof $token
- } else {
- CopyStart $s $token
- }
- }
-
-
- proc http::Eof {token} {
- variable $token
- upvar 0 $token state
- if {$state(state) eq "header"} {
- set state(status) eof
- } else {
- set state(status) ok
- }
- set state(state) eof
- Finish $token
- }
-
-
- proc http::wait {token} {
- variable $token
- upvar 0 $token state
-
- if {![info exists state(status)] || [string length $state(status)] == 0} {
- vwait $token\(status)
- }
-
- return $state(status)
- }
-
-
- proc http::formatQuery {args} {
- set result ""
- set sep ""
- foreach i $args {
- append result $sep [mapReply $i]
- if {$sep eq "="} {
- set sep &
- } else {
- set sep =
- }
- }
- return $result
- }
-
-
- proc http::mapReply {string} {
- variable http
- variable formMap
-
-
- if {$http(-urlencoding) ne ""} {
- set string [encoding convertto $http(-urlencoding) $string]
- return [string map $formMap $string]
- }
- set converted [string map $formMap $string]
- if {[string match "*\[\u0100-\uffff\]*" $converted]} {
- regexp {[\u0100-\uffff]} $converted badChar
- return -code error "can't read \"formMap($badChar)\": no such element in array"
- }
- return $converted
- }
-
-
- proc http::ProxyRequired {host} {
- variable http
- if {[info exists http(-proxyhost)] && [string length $http(-proxyhost)]} {
- if {![info exists http(-proxyport)] || ![string length $http(-proxyport)]} {
- set http(-proxyport) 8080
- }
- return [list $http(-proxyhost) $http(-proxyport)]
- }
- }
-